package com.qingzhu.component.lock.way.redis.generator;

import com.qingzhu.component.lock.annotation.generator.LockGeneratorBean;
import com.qingzhu.component.lock.common.cache.LockCache;
import com.qingzhu.component.lock.common.cache.expire.strategy.CacheOutStrategy;
import com.qingzhu.component.lock.common.config.LockProperties;
import com.qingzhu.component.lock.common.config.enums.RedisEnumConfig;
import com.qingzhu.component.lock.common.config.enums.spi.SpringbootSpiConfigEnums;
import com.qingzhu.component.lock.common.constant.locktype.LockTypeConstant;
import com.qingzhu.component.lock.common.entity.LockEntity;
import com.qingzhu.component.lock.common.exception.LockConfigException;
import com.qingzhu.component.lock.common.generator.LockGenerator;
import com.qingzhu.component.lock.common.pool.ForkLockExecutor;
import com.qingzhu.component.lock.common.util.BaseUtil;
import com.qingzhu.component.lock.way.redis.entity.RedisLockEntity;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.ClusterServersConfig;
import org.redisson.config.Config;
import org.redisson.config.SentinelServersConfig;
import org.redisson.config.SingleServerConfig;
import org.springframework.beans.factory.annotation.Value;

import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * redis的锁生成器
 * @author xiangjz
 * @version 1.0
 * @date 2021/1/14 17:09
 */
@LockGeneratorBean(value = "redisLockGenerator", type = LockTypeConstant.LOCK_TYPE_REDIS)
public class RedisLockGenerator extends LockGenerator<String> {

    private final RedissonClient redissonClient;
    private final String lockPrefix;
    
    private final static String REDIS_HOST_PREFIX = "redis://";

    public RedisLockGenerator(CacheOutStrategy cacheOutStrategy,
                              LockProperties lockProperties,
                              @Value("${spring.application.name}") String applicationName,
                              ForkLockExecutor forkLockExecutor) {
        super(cacheOutStrategy, forkLockExecutor);
        Map<String, Object> redisConfig = lockProperties.getRedis();
        if(BaseUtil.isEmpty(redisConfig)) {
            throw new LockConfigException("redisConfig is invalid, please make sure that config [redis] exist below root config [lock]");
        }
        this.lockPrefix = "lock-" + (BaseUtil.isEmpty(applicationName) ? "" : applicationName + "-");
        String password = BaseUtil.retStr(RedisEnumConfig.getOrDefault(redisConfig, RedisEnumConfig.CONNECTION_PASSWORD));

        Config config = null;
        // 检查是否配置了single、sentinel、cluster中的，有且仅有一种
        // single的配置
        Map<String, Object> redisSingleConfig = (Map<String, Object>) redisConfig.get(SpringbootSpiConfigEnums.LockRedisServerModeEnum.SINGLE.getMode());
        if(!BaseUtil.isEmpty(redisSingleConfig)) {
            String host = BaseUtil.retStr(RedisEnumConfig.RedisSingleEnumConfig.getOrDefault(redisSingleConfig, RedisEnumConfig.RedisSingleEnumConfig.CONNECTION_HOST));
            String port = BaseUtil.retStr(RedisEnumConfig.RedisSingleEnumConfig.getOrDefault(redisSingleConfig, RedisEnumConfig.RedisSingleEnumConfig.CONNECTION_PORT));
            if(!BaseUtil.isEmpty(host) && !BaseUtil.isEmpty(port)) {
                // singleNode配置有效
                config = new Config();
                String connectionUrl = REDIS_HOST_PREFIX + host + ":" + port;
                SingleServerConfig singleServerConfig = config.useSingleServer().setAddress(connectionUrl);
                if(!BaseUtil.isEmpty(password)) {
                    singleServerConfig.setPassword(password);
                }
            }
        }

        // sentinel的配置
        Map<String, Object> redisSentinelConfig = (Map<String, Object>) redisConfig.get(SpringbootSpiConfigEnums.LockRedisServerModeEnum.SENTINEL.getMode());
        if(!BaseUtil.isEmpty(redisSentinelConfig)) {
            if(config != null) {
                throw new LockConfigException("redisConfig is invalid, cause is multi mode config," +
                        " please make sure that your redis server mode is only one of [single, sentinel, cluster] " +
                        " below config [lock.redis]");
            }
            config = new Config();
            String sentinelMasterName = BaseUtil.retStr(RedisEnumConfig.RedisSentinelEnumConfig.getOrDefault(redisSentinelConfig, RedisEnumConfig.RedisSentinelEnumConfig.MASTER_NAME));
            Collection<String> sentinelAddresses = ((LinkedHashMap) RedisEnumConfig.RedisSentinelEnumConfig.getOrDefault(redisSentinelConfig, RedisEnumConfig.RedisSentinelEnumConfig.ADDRESSES)).values();
            SentinelServersConfig sentinelServersConfig = config.useSentinelServers().setMasterName("lockSentinel-" + applicationName + "-" + sentinelMasterName);
            if(!BaseUtil.isEmpty(sentinelAddresses)) {
                sentinelAddresses.forEach(address ->{
                    if(!address.startsWith(REDIS_HOST_PREFIX)) {
                        address = REDIS_HOST_PREFIX + address;
                    }
                    sentinelServersConfig.addSentinelAddress(address);
                });
            }
            if(!BaseUtil.isEmpty(password)) {
                sentinelServersConfig.setPassword(password);
            }
        }

        // cluster的配置
        Map<String, Object> redisClusterConfig = (Map<String, Object>) redisConfig.get(SpringbootSpiConfigEnums.LockRedisServerModeEnum.CLUSTER.getMode());
        if(!BaseUtil.isEmpty(redisClusterConfig)) {
            if(config != null) {
                throw new LockConfigException("redisConfig is invalid, cause is multi mode config," +
                        " please make sure that your redis server mode is only one of [single, sentinel, cluster] " +
                        " below config [lock.redis]");
            }
            config = new Config();
            int clusterScanInterval = BaseUtil.retInt(RedisEnumConfig.RedisClusterEnumConfig.getOrDefault(redisClusterConfig, RedisEnumConfig.RedisClusterEnumConfig.SCAN_INTERVAL));
            Collection<String> clusterAddresses = ((LinkedHashMap) RedisEnumConfig.RedisClusterEnumConfig.getOrDefault(redisClusterConfig, RedisEnumConfig.RedisClusterEnumConfig.ADDRESSES)).values();
            ClusterServersConfig clusterServersConfig = config.useClusterServers().setScanInterval(clusterScanInterval);
            if(!BaseUtil.isEmpty(clusterAddresses)) {
                clusterAddresses.forEach(address -> {
                    if(!address.startsWith(REDIS_HOST_PREFIX)) {
                        address = REDIS_HOST_PREFIX + address;
                    }
                    clusterServersConfig.addNodeAddress(address);
                });
            }
            if(!BaseUtil.isEmpty(password)) {
                clusterServersConfig.setPassword(password);
            }
        }
        if(config == null) {
            throw new LockConfigException("redisConfig is invalid, no correct redis server config");
        }

        redissonClient = Redisson.create(config);
    }

    @Override
    public LockEntity doGetLock(String key, long expireTime) {
        return new RedisLockEntity(key, expireTime, redissonClient, this.lockPrefix + key);
    }

}
